VBcrackme 解析手引き「VBCrack3」


始める前に、補助ツール ExDec(VB DeComplier)で、P-Codeリストは作成してますね?

では、WKTVBDE を起動します。
VBCrack3.exe を読込んで下さい。
すばやく「Action」→「Run」又は、ツールバーの「Run」で実行します。
WKTVBDebuggerのウィンドウと、「VB Crack Me #3」のウィンドウが表示されます。

次に、ブレークポイントの設定に入ります。
「Form Manager (Ctrl+F)」で、Form Managerウィンドウを表示します。
コンボボックスの▼ボタンをクリックして、「frmMain」を選択して下さい。
すると、「VB Crack Me #3」ウィンドウで使用されてるコントロールのボタンが有効になります。
(「Label」、「TextBox」、「Command」の3つ)

「Command」ボタンをクリックします。
ウィンドウが、ポップアップします。
コンボボックスの▼ボタンをクリックすると、「cmdOK」と「cmdQuit」と表示されますので、
「cmdOK」を選択します。
すると、いろいろと「cmdOK」ボタンに関する情報が表示されます。
ここで、「BPX」のボタンをクリックします。
一応、ブレークポイントが有効か確認してみましょう。
「On Execution (Ctrl+E)」をクリックします。
「Current BPX and BPM List」ウィンドウが表示されます。
確認できましたか?
もし邪魔であれば、「Form Maneger」のウィンドウを閉じて構いません。 (閉じるは、「×」ボタンクリック)
この「Current BPX and BPM List」ウィンドウも閉じて構いません。

フェーク・シリアル「12345-67890-abcde-fghij」と入力して、「登録」ボタンをクリックします。
(シリアル入力時に、1度 WKTVBDEがアクティブになります。 「Go!(F5)」で実行を続けてください。)
すると、0x4027E0でブレークし、P-Codeリストが表示されます。


4027E0: 1B LitStr: '不正シリアルです。'


と表示されてます。 (Win9x系OSでは、文字化けしてると思います。)
P-Code(ExDec)リストの 「402800: 0d VCallHresult get__ipropTEXTEDIT」辺りで、入力シリアルの読込みをしていると思われます。
ぉ! WKTVBDEのP-CodeとExDecのリストが、違う事に気づきましたか?
タイミング的に、ExDecのP-Codeがトレースするのに適していると思われますので、ExDecのP-Code表記にしてあります。
あくまでも独断です。

アドレス 0x402800 まで実行します。
ここでスタック域の先頭4バイトに注目します。
私の環境では、0x63F358っとなってます。
注:スタック域のアドレスなので環境により変わります。 表示されてる値に読替えてください。

1ステップ実行します。 (「Step Trace(F8)」)
0x63F358の内容を見てみましょう。(「Memory Dump(Ctrl+M)」を使用します。)
先頭4バイトにアドレス値(私の環境では、0x42B868)が入っています。
さらに、0x42B868 の内容を参照します。
入力シリアル(先頭のテキスト・ボックス)の文字が入っています。(UNICODE形式)
WKTVBDEのP-Codeリストを見ます。


402805: f5 LitI4: -> 0x1 1
40280A: 6c ILdRf 0042B868h        ← 入力シリアルが入っているアドレス値
40280D: 0b ImpAdCallI2 rtcLeftCharBstr


rtcLeftCharBstrは、VB記述のLeft$()関数、引数は、1と、入力シリアル文字です。
この3行で、先頭入力シリアルの1文字を取得しています。

アドレス 0x402812 まで実行します。
すると、ログ表示部分に「FStStrNoPop -> '1'」と表示されます。(Left$の結果)
また、WKTVBDEのP-Codeリストを見ます。


402815: 1b LitStr: 'K'
402818: Lead0/30 EqStr


Left$の結果と、文字列の'K'との比較ですね。

アドレス 0x40281A まで実行します。
この時のスタック域の4バイトが、EqStrの比較結果です。
結果は、'1'と'K'を比較しているので、0(ゼロ)-falseです。
この値を、0xffffffff-true に変更します。
変更方法は、「Memory Dump(Ctrl+M)」で、スタックアドレスを入力して
先頭の値 Wクリック後に、4バイト 0xffに変更後、「Patch Now!」です。

またまた、WKTVBDEのP-Codeを見ると、数行下に


402834: 0d VCallHresult get__ipropTEXTEDIT


が見つかります。
あ! ExDecのP−codeリストと同じですね。
入力シリアルの読込みのようですね。

アドレス 0x402834 まで実行します。
この時のスタック域4バイトのアドレスが、0x63F348となってます。
1ステップ実行し、0x63F348 の内容を参照すると、アドレス値(0x42E28C)が入っています。
0x42E28C の内容を参照すると、入力シリアル(先頭のテキスト・ボックス)の文字が入っています。
WKTVBDEのP-Codeを見ると、


402839: 6c ILdRf 0042ACB0h
40283C: 4a FnLenStr      ← len(先頭のテキスト・ボックスの文字) : 文字数取得
40283D: f5 LitI4: -> 5h 5
402842: c7 EqI4        ← 定数5 と 40283C での文字数の比較
402843: c4 AndI4        ← 上の比較結果値と、シリアル先頭文字='K'の結果値のAnd
402858: 1c BranchF: 402B43   アドレス 402843 の結果が、0(false)の時、チェック処理の最後へジャンプ


まとめ(その1)
「一番最初のシリアル入力域には、先頭が'K'で始まる5文字でね。」
先ほど、先頭文字'K'の比較結果は、true値に変更しました。 文字列の長さも5文字入力したので気にせず進めます。

では、アドレス 0x402844 まで実行します。
スタック域の4バイトは、AndI4の結果 0xffffffff(true)が入っています。
また、P-Code(ExDec)リストを見ると、


402875: 0d VCallHresult get__ipropTEXTEDIT
402894: 0d VCallHresult get__ipropTEXTEDIT


の2箇所で、入力シリアルの読込みを行っています。
この読込み結果を見ていきます。

アドレス 0x402875 まで実行します。
この時の、スタック域4バイトのアドレス値(0x63F358)をメモして1ステップ実行し、
0x63F358 の内容を参照すると、アドレス値(0x42B868)が入っています。
0x42B868 の内容を参照すると、入力シリアル(先頭のテキスト・ボックス)の文字ですね。

次に、アドレス 0x402894 まで実行します。
また、スタック域4バイトのアドレス値(0x63F348)をメモして1ステップ実行し、
0x63F348 の内容を参照すると、アドレス値(0x42AF38)が入っています。
0x42AF38 の内容を参照すると、これも入力シリアル(先頭のテキスト・ボックス)の文字ですね。


402899: 28 LitVarI2: 1h, 1
40289E: f5 LitI4: -> 3h 3
4028A3: 6c ILdRf 0042AF38h
4028A6: 0b ImpAdCallI2 rtcMidCharBstr


ここで、VB表記 Mid$("12345", 3, 1) {"12345"の文字列から3文字目の1文字抽出}

アドレス 0x4028AB まで実行します。
ログ表示部分に「FStStrNoPop -> '3'」と表示されます。(Mid$の結果)
カレント行の「FStStrNoPop 0063F304h」のスタックアドレス値と、オペランドコード 23 00 FF の
相対オフセット値 0xFF00 をメモしといてください。後ほど使います。


4028AE: 0a ImpAdCallFPR4: rtcR8ValFromBstr 


で、 Mid$の結果'3'を数値に変換します。
変換後の数値形式は、倍精度浮動小数点(8バイト)です。

アドレス 0x4028B3 まで実行します。
ここで変換後の値は入るアドレスが、WKTVBDE には表示されてません。(謎です)
では、結果が入るアドレスを計算してみましょう。
先程、メモしたスタックアドレス値とオペランドコードの相対オフセット値を用いて計算してみましょう。
まず、スタック域の先頭と思われるアドレスが、WKTVBDE のタイトルバーの下、
「Locs. Addr; xxxxxxxxh」に表示されてる値であると思います。
私の環境では、スタック先頭アドレスは、0x63F3AC です。
0x63F3AC + 0xFFFFFF00(相対オフセット値) = 0x63F2AC です。
実際は、0x63F304 と 計算結果より 0x58 バイト大きいアドレスです。
何故 0x58 バイト大きいのかわかりません。 (解決できたら教えてください。)
悩んでいても進まないので、補正値として使ってしまいます。

では、FStFPR8 のアドレス値を算出します。
0x63F3AC + 0xFFFFFEE8 + 0x58 = 0x63F2EC となります。
1ステップ実行し、0x63F2EC の内容を参照してみましょう。


63F2EC: 00 00 00 00 00 00 08 40


ですね。 倍精度浮動小数点形式で、値 3.0です。
アドレス 0x63F2EC を以降 local_0118 と記します。
P-Codeリストを見ると


4028B6: 28 LitVarI2: 1h 1
4028BB: f5 LitI4: -> 2h 2
4028C0: 6c ILdRf 0042B868h       → "12345"
4028C3: 0b ImpAdCallI2 rtcMidCharBStr


ここで、VB表記 Mid$("12345", 2, 1) {"12345"の文字列から2文字目の1文字抽出}

アドレス 0x4028C8 まで実行します。
ログ表示部分に「FStStrNoPop -> '2'」と表示されます。(Mid$の結果)


4028CB: 0a ImpAdCallFPR4: rtcR8ValFromBstr 


で、 Mid$の結果'2'を倍精度浮動小数点の数値に変換します。

アドレス 0x4028D0 まで実行します。
カレント行以降に


4028D0: f4 LitI2_Byte: -> 2h 2
4028D2: eb CR8I2           → 整数 2 を、倍精度浮動小数点に変換
4028D3: b3 MulR8           → 倍精度浮動小数点の掛算 ( Mid$の結果'2' × 2)
4028D4: f3 LitI2: -> 3E8h 1000
4028D7: eb CR8I2           → 整数 1000 を、倍精度浮動小数点に変換
4028D8: b3 MulR8           → アドレス 0x4028D3 の結果 × 1000


アドレス 0x4028D9 まで実行します。


4028D9: 6f FLdFPR8 local_0118 

です。
local_0118 から内容を読込んでいます。
また、カレント行から数行掛算等の演算が続いてます。


4028DC: f4 LitI2_Byte: -> 4h 4
4028DE: eb CR8I2           → 整数 4 を、倍精度浮動小数点に変換
4028DF: b3 MulR8           → local_0118 × 4
4028E0: ab AddR8           → 上記の結果 + アドレス 04028D8 の結果


アドレス 0x4028E1 まで実行します。


4028E1: Lead2/CVarR8 

このオペランドの相対オフセットから、スタックアドレスを算出します。 (0x63F2F4)
1ステップ実行し、0x63F2F4 の内容を参照すると、


0063F2F4: 05 00 00 00 00 00 00 00  ← バリアント変数のタイプ(倍精度浮動小数点)
0063F2FC: 00 00 00 00 00 58 AF 40  ← 値


今までの演算結果を、バリアント変数に変換です。
1ステップ実行し、また オペランドの相対オフセットから、スタックアドレスを算出します。 (0x63F36C)
ここで、0x63F2F4 → 0x63F36C に、倍精度浮動小数点値を代入してます。
アドレス 0x63F36C を以降 local_0098 と記します。

アドレス 


0x402920 : 0d VCallHresult get__ipropTEXTEDIT 


まで実行します。
スタック域にあるアドレス値を控えます。(0x63F358)
1ステップ実行し、0x63F358 の内容を参照すると、アドレス値(0x42B82C)が入っています。
0x42B82C の内容を参照すると、入力シリアル(2番目のテキスト・ボックス)の文字ですね。

アドレス 0x402941 まで実行します。
すると、3行下に 「0040294A: 0B ImpAdCallI2 rtcbstrFromFormatVar」と表示されます。(VBのFormat文です。)

さらに、アドレス 0x40294A まで実行します。
ここで、local_0098 の値を、'00000'形式の文字列にしてます。
1ステップ実行すると、ログ表示部分に「FStStrNoPop -> '04012'」と表示されます。
そして、なんとP-Code(WKTVBDE)リストの下のほうに、見慣れたAPIが・・・・


402963: 5e ImpAdCallI4 kernel32!lstrcmpA 


があります。

アドレス 0x402963 まで実行します。
lstrcmpAの引数は、スタック域にあります。
0x42E28C → '67890', 0x42B818   → '04012', 5
フェークシリアルと正解シリアルの比較ですね。
lstrcmpAの結果を、「402974: c7 EqI4」でチェックしてます。

アドレス 0x402974 まで実行します。
lstrcmpAの結果=0がのチェックですね。
1ステップ実行して、スタック域の4バイトを、 0xffffffff (true)に変更します。

まとめ(その2)
「Kabcd-XXXXX とすると XXXXX = a * 2 * 1000 + b * 4 となります。」

P-Code(ExDec)のリストを見ます。


4029B5: 0d VCallHresult get__ipropTEXTEDIT
4029D4: 0d VCallHresult get__ipropTEXTEDIT


の2箇所で、入力シリアルの読込みをしてますね。
また、何を読んでいるか見ていきましょう。

アドレス 0x4029B5 まで実行します。
ここでスタック域にあるアドレスをメモ(0x63F358)
1ステップ実行し、0x63F358 の内容を参照すると、アドレス値(0x42B82C)が入っています。
0x42B82C の内容を参照すると、入力シリアル(先頭のテキスト・ボックス)の文字ですね。

アドレス 0x4029D4 まで実行します。
スタック域にあるアドレスをメモ(0x63F348)
1ステップ実行し、0x63F348 の内容を参照すると、アドレス値(0x42E28C)が入っています。
0x42E28C の内容を参照すると、またもや、入力シリアル(先頭のテキスト・ボックス)の文字ですね。


4029D9: 28 LitVarI2: 1h 1
4029DE: f5 LitI4: -> 5h 5
4029E3: 6c ILdRf 0042E28Ch          → "12345"
4029E6: 0b ImpAdCallI2 rtcMidCharBstr    → Mid$("12345", 5, 1)='5'
4029EB: 23 FStStrNoPop
4029EE: 0a ImpAdCallFPR4: rtcR8ValFromBstr  → Mid$("12345", 5, 1)の結果を倍精度浮動小数点に変換
4029F3: 74 FStFPR8


アドレス 0x4029F3 まで実行します。
ここの時にまた、相対オフセットからスタックアドレスを計算しますが、オフセット値 0xFEE8 さっき計算しましたね。
local_0118 と同じアドレスです。


4029F6: 28 LitVarI2: 1h , 1
4029FB: f5 LitI4: -> 4h 4
402A00: 6c ILdRf 0042E28Ch          → "12345"
402A03: 0b ImpAdCallI2 rtcMidCharBstr     → Mid$("12345", 4, 1)='4'
402A08: 23 FStStrNoPop
402A0B: 0a ImpAdCallFPR4: rtcR8ValFromBstr  → Mid$("12345", 4, 1)の結果を倍精度浮動小数点に変換


アドレス 0x402A0B まで実行します。
次からは、演算の固まりですね。


402A10: f4 LitI2_Byte: 19h 25
402A12: eb CR8I2        → 25を倍精度浮動小数点に変換
402A13: b3 MulR8        → Mid$の結果(4) × 25
402A14: 6f FLdFPR8 local_0118  の読込み
402A17: ab AddR8        → local_0118 + 402A13 の結果
402A18: e5 CI2R8        → 倍精度浮動小数点を2バイトIntegerに変換
402A19: 70 FStI2 local_009A


アドレス 0x402A19 まで実行します。
すると、


402A19: 70 FStI2 0063F36A -> 69h 105 


っと表示されます。
Integer値が、0x68(105)で、0x63F36Aに代入されます。
下の3行は、ワーク領域の開放です。
4行目から、また演算が始まりますね。


402A39: 6b FLdI2 local_009A      の読込み (0x69)
402A3C: eb CR8I2           → 0x69 を倍精度浮動小数点に変換
402A3D: f4 LitI2_Byte: -> 3h 3
402A3F: eb CR8I2           → 3 を倍精度浮動小数点に変換
402A40: b6 DivR8 → 0x69 ÷ 3
402A41: f4 LitI2_Byte: -> 7h 7
402A43: eb CR8I2           → 7 を倍精度浮動小数点に変換
402A44: b3 MulR8           → (0x69 ÷ 3) × 7
402A45: e5 CI2R8           → 402A44 の結果を、2バイトIntegerに変換
402A46: 70 FStI2 local_009A


アドレス 0x402A46 まで実行します。
すると、


402A46: 70 FStI2 0063F36A -> f5h 245 


っと表示されます。
Integer値が、0xf5(245)で、0x63F36Aに代入されます。
次に、


402A63: 0d VCallHresult get__ipropTEXTEDIT


の入力シリアルの読込み部分を見てみましょう。

アドレス 0x402A63 まで実行します。
ここでいつもの用に、スタック域のアドレス値をメモ! (0x63F358)
1ステップ実行し、0x63F358 の内容を参照すると、アドレス値(0x42B868)が入っています。
0x42B868 の内容を参照すると、入力シリアル(3番目のテキスト・ボックス)の文字ですね。


402A72: 3a LitVarStr: '00000'
402A77: 4e FStVarCopyObj local_00DC
402A7A: 04 FLdRfVar local_00DC
402A7D: 04 FLdRfVar local_009A         → 0xf5(245)
402A80: 4d CVarRef: ( local_00CC ) 4002
402A85: 0b ImpAdCallI2 rtcBstrFromFormatVar  → Format$('00000', 245)
402A8A: 23 FStStrNoPop local_00BC


と、Format文ですね。

アドレス 0x402A8A まで実行します。
ログ表示部分に、「FStStrNoPop -> '00245'」っとFormat文の結果が表示されます。
ここでまた、例のAPIが・・・・・


402A9E: 5e ImpAdCallI4 kernel32!lstrcmpA 


があります。

アドレス 0x402A9E まで実行します。
スタック域のアドレスから
0x42AF38 → 'abcde', 0x42B818 → '00245', 5
フェークシリアルと正解シリアルの比較ですね。
lstrcmpAの結果を、「402AAF: c7 EqI4」でチェックしてます。

アドレス 0x402AAF まで実行します。
lstrcmpAの結果=0がのチェックですね。
1ステップ実行して、スタック域の4バイトを、 0xffffffff (true)に変更します。

まとめ(その3)
「Kabcd-XXXXX-YYYYY とすると YYYY = (c * 25 + d) / 3 * 7 となります。」

また、P-Code(ExDec)のリストを見ます。


402AC8: 04 FLdRfVar local_0098      → 4012 (倍精度浮動小数点)
402ACB: 6b FLdI2 local_009A       → 0xf5(245)
402ACE: 44 CVarI2 local_00CC             → 4012 (倍精度浮動小数点)を、2バイトIntegerに変換
402AD1: Lead0/94 AddVar local_00DC    → 245 + 4012
402AD5: 28 LitVarI2: 2710h 10000
402ADA: Lead0/a4 ModVar         → (245 + 4012) Mod 10000
402ADE: Lead1/f6 FStVar local_0098


アドレス 0x402ADE まで実行します。
もう少しです。 頑張りましょう! 
ここで、相対オフセット値からスタックアドレスを算出します。 (0x63F36C)
1ステップ実行し、0x63F36C の内容を参照すると、


0063F36C: 03 00 00 00 00 00 00 00  ← バリアント変数のタイプ(Long)
0063F374: A1 10 00 00 00 00 00 00  ← 値


次に、


402AFF: 0d VCallHresult get__ipropTEXTEDIT


で読込んでいる入力シリアルの読込み部分を見てみましょう。

アドレス 0x402AFF まで実行します。
ここで、スタック域のアドレス メモしておきましょう。 (0x63F358)
1ステップ実行し、0x63F358 の内容を参照すると、アドレス値(0x42B868)が入っています。
0x42B868 の内容を参照すると、入力シリアル(4番目のテキスト・ボックス)の文字ですね。


402B11: 3a LitVarStr: '0000'
402B16: 4e FStVarCopyObj local_00DC
402B19: 04 FLdRfVar local_00DC
402B1C: 04 FLdRfVar local_0098          → 0x1a1 (0x63F36C) を参照
402B1F: 0b ImpAdCallI2 rtcBstrFromFormatVar   → Format$('0000', 0x1a1)
402B24: 23 FStStrNoPop local_00B0


と、Format文です。

アドレス 0x402B24 まで実行します。
次のオペランドが、「30 EqStr」となってますね。
文字列の比較ですね。

1ステップ実行します。
ここで、スタック域にアドレスが2つあります。
1つ目は、0x42B818 → "4257" UNICODE形式 正解シリアル
2つ目は、0x42B868 → "fghij" UNICODE形式 フェークシリアル

1ステップ実行します。
スタック域に、比較結果 0(false)が入ります。
また、スタック域4バイトを、0xffffffff(true)に変更します。
あとは、思いっきり、「Go!(F5)」です。

まとめ(その4)
「Kabcd-XXXXX-YYYYY-ZZZZ とすると ZZZZ = (XXXXX + YYYYY) Mod 10000 となります。」
今回の正解シリアル値は、 K2345-04012-00245-4257  です。

大変長い説明になりましたが、これで終わりです。
次回は、講義するまえに「VBCrack1」のパッチイメージ募集します。

キャプテン・ジャックの指令:「次回までにナグ消せ!」「連絡方法は、追って板に打ちつける。」

と受信しました。∠(  ̄(工) ̄)